# 辅助理解，实际开发不会使用裸API的faction calling
from openai import OpenAI
from globle import configs as cfg
import json
'''
function_call作用：让大模型调用自定义的方法
代码编写宏观逻辑：
1. 先准备好client与调用llm的函数 send_messages()
2. 准备好自定义的方法 add()
3. 调用llm时，配置tools参数，细节详见tools注释部分。

# 以下部分详见do()
4. 若需要触发自定义的方法，需要先调用1次llm,llm会根据prompt与配置的tool判断是否要触发自定义的方法以及顺便提取方法所需的参数。
5. 将提取的参数传入自定义方法，获取方法结果后添加到上下文中。在此之前还需要将第一次调用LLM时的AI返回也加入到上下文中。
6. 最后再调用次LLM，传入包裹整个流程的messages.

'''


client = OpenAI(
    api_key=cfg.DEEPSEEK_API_KEY,
    base_url="https://api.deepseek.com",
)
def send_messages(messages):
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=messages,
        tools=tools,
    )
    return response.choices[0].message

tools = [ # 定义工具, 可添加多个
    {
        "type": "function",
        "function": {
            "name": "add", # 对应自定义方法名
            "description": "一个加法工具", # LLM会根据此描述判断是否要调用此工具
            "parameters": { # 定义方法所需的参数, 若方法参数为空，则设置为{}或省略parameters
                "type": "object",
                "properties": {
                    "a": {"type": "integer", "description": "第一个数字"}, # LLM会根据参数描述在prompt中提取参数
                    "b": {"type": "integer", "description": "第二个数字"},
                },
                "required": ["a", "b"], # 告知LLM这些参数是必须的
            },
        }
    }
]

# 自定义的方法
def add(a, b):
    # 注意方法名字与tools定义的name一致
    # 注意参数名与tools定义的参数名一致
    return a + b

# 定义函数注册表
function_registry = {
    "add": add
}

def do():
    # 用户提问
    messages = [{"role": "user", "content": "2+3等于几"}]
    print(f"User>\t {messages[0]['content']}")

    # 第一次调用
    assistant_message = send_messages(messages)
    print("Model response:", assistant_message)

    # 检查是否有工具调用
    if assistant_message.tool_calls:
        tool_call = assistant_message.tool_calls[0]
        function_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)  # 解析参数

        # 执行工具函数
        result = function_registry[function_name](**function_args)
        print(f"Tool Result: {result}")

        # 把最初LLM的返回加入到上下文
        messages.append({
            "role": "assistant",
            "content": None,
            "tool_calls": assistant_message.tool_calls  # 保留 tool_calls
        })

        # 将工具执行结果加入到上下文
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,  # 必须匹配之前的 tool_call_id
            "name": function_name,  # 可选，但建议提供
            "content": str(result),  # 必须转为字符串
        })

        # 第二次调用：模型返回最终回答
        final_response = send_messages(messages)
        print(f"Model>\t {final_response.content}")
    else:
        print("没有触发工具")


if __name__ == '__main__':
    do()